#ifndef SERIALPORT_H_
#define SERIALPORT_H_

#include <boost/integer.hpp>
#include <string>
#include <vector>
#include <queue>
#include <stdexcept>
#include <cassert>
#include <termios.h>
#include "SignalHandler.h"
#include "DataStream.h"

namespace uav
{
namespace comm
{

/**
 * @class SerialPort
 *
 * @brief Utility class providing access to serial ports on POSIX systems.
 *
 * This class is based on the libserial library.
 *
 * @author Travis Portz
 */
class SerialPort : public SignalHandler, public DataStream
{
public:

	enum BaudRate
	{
		BAUD_50      = B50,
		BAUD_75      = B75,
		BAUD_110     = B110,
		BAUD_134     = B134,
		BAUD_150     = B150,
		BAUD_200     = B200,
		BAUD_300     = B300,
		BAUD_600     = B600,
		BAUD_1200    = B1200,
		BAUD_1800    = B1800,
		BAUD_2400    = B2400,
		BAUD_4800    = B4800,
		BAUD_9600    = B9600,
		BAUD_19200   = B19200,
		BAUD_38400   = B38400,
		BAUD_57600   = B57600,
		BAUD_115200  = B115200,
		BAUD_230400  = B230400,
		BAUD_DEFAULT = BAUD_57600
	};

	enum CharSize
	{
		CHAR_SIZE_5  = CS5, ///< 5 bit characters.
		CHAR_SIZE_6  = CS6, ///< 6 bit characters.
		CHAR_SIZE_7  = CS7, ///< 7 bit characters.
		CHAR_SIZE_8  = CS8, ///< 8 bit characters.
		CHAR_SIZE_DEFAULT = CHAR_SIZE_8
	};

	enum StopBits
	{
		STOP_BITS_1,   ///< 1 stop bit.
		STOP_BITS_2,   ///< 2 stop bits.
		STOP_BITS_DEFAULT = STOP_BITS_1
	};

	enum Parity
	{
		PARITY_EVEN,     ///< Even parity.
		PARITY_ODD,      ///< Odd parity.
		PARITY_NONE,     ///< No parity.
		PARITY_DEFAULT = PARITY_NONE
	};

	enum FlowControl
	{
		FLOW_CONTROL_HARD,
		FLOW_CONTROL_NONE,
		FLOW_CONTROL_DEFAULT = FLOW_CONTROL_NONE
	};

	/**
	 * @brief Exception thrown when an attempt at opening a serial port fails.
	 */
	class OpenFailed : public std::runtime_error
	{
	public:
		OpenFailed(const std::string &str) : runtime_error(str) {}
	};

	/**
	 * @brief Exception thrown when an attempt at setting the baud rate fails.
	 */
	class UnsupportedBaudRate : public std::runtime_error
	{
	public:
		UnsupportedBaudRate() : runtime_error("Unsupported baud rate.") {}
		UnsupportedBaudRate(const std::string &str) : runtime_error(str) {}
	};

	/**
	 * Constructs a serial port connection for the given port name.
	 */
	explicit SerialPort(const std::string &portName);

	/**
	 * Destructs the serial port. If the port is still open, it is closed.
	 */
	~SerialPort();

	/**
	 * Opens the serial port with the provided settings.
	 */
	void open(BaudRate    baudRate    = BAUD_DEFAULT,
	          CharSize    charSize    = CHAR_SIZE_DEFAULT,
	          Parity      parity      = PARITY_DEFAULT,
	          StopBits    stopBits    = STOP_BITS_DEFAULT,
	          FlowControl flowControl = FLOW_CONTROL_DEFAULT)
		throw(OpenFailed, UnsupportedBaudRate, std::invalid_argument);

	/**
	 * Checks if the serial port is open.
	 */
	bool isOpen() const;

	/**
	 * Closes the serial port.
	 */
	void close() throw(NotOpen);

	/**
	 * Sets the baud rate of the serial port.
	 */
	void setBaudRate(BaudRate baudRate) throw(UnsupportedBaudRate, std::invalid_argument);

	/**
	 * Gets the current baud rate of the serial port.
	 */
	BaudRate getBaudRate() const throw(std::runtime_error);

	/**
	 * Sets the character size of the serial port.
	 */
	void setCharSize(CharSize charSize) throw(std::invalid_argument);

	/**
	 * Gets the current character size of the serial port.
	 */
	CharSize getCharSize() const;

	/**
	 * Sets the parity type of the serial port.
	 */
	void setParity(Parity parity) throw(std::invalid_argument);

	/**
	 * Gets the current parity type of the serial port.
	 */
	Parity getParity() const;

	/**
	 * Sets the number of stop bits to be used by the serial port.
	 */
	void setStopBits(StopBits stopBits) throw(std::invalid_argument);

	/**
	 * Gets the number of stop bits currently being used by the serial port.
	 */
	StopBits getStopBits() const;

	/**
	 * Set flow control.
	 */
	void setFlowControl(FlowControl flowControl) throw(std::invalid_argument);

	/**
	 * Gets the current flow control setting.
	 */
	FlowControl getFlowControl() const;

	/**
	 * Checks if data is available at the input of the serial port.
	 */
	bool isDataAvailable() const;

	/**
	 * Read a byte from the serial port.
	 */
	uint8_t readByte();

	/**
	 * Reads the specified number of bytes from the serial port into @a buffer.
	 *
	 * The buffer will be emptied before data is read into it.
	 *
	 * If @a numBytes is 0, the function will keep reading data until there is
	 * none unavailable.
	 */
	void read(std::vector<uint8_t> &buffer, int numBytes = 0);

	/**
	 * Reads a line of characters from the serial port up to @e and @e including
	 * the specified terminator.
	 */
	std::string readLine(char terminator = '\n');

	/**
	 * Sends a byte to the serial port.
	 *
	 * @return True on success, false on error.
	 */
	bool writeByte(uint8_t dataByte);

	/**
	 * Writes the data from the provided vector to the serial port.
	 *
	 * @return True on success, false on error.
	 */
	bool write(const std::vector<uint8_t> &buffer);

	/**
	 * Writes a string to the serial port.
	 *
	 * @return True on success, false on error.
	 */
	bool write(const std::string &str);

	void handleSignal(int sigNum);

private:
	std::string portName;
	bool opened;
	int fileDescriptor;
	termios oldPortSettings;
	std::queue<uint8_t> inputBuffer;

	/**
	 * Writes the data from the provided buffer to the serial port.
	 *
	 * @return True on success, false on error.
	 */
	bool write(const uint8_t *buffer, int size);

	// Prevent copying
	SerialPort(const SerialPort &port) {}
	SerialPort& operator=(const SerialPort &port) { return *this; }
};

inline SerialPort::SerialPort(const std::string &portName)
	: portName(portName), opened(false), fileDescriptor(-1), oldPortSettings() {}

inline SerialPort::~SerialPort()
{
	if (isOpen()) close();
}

inline bool SerialPort::isOpen() const
{
	return opened;
}

} // end namespace comm
} // end namespace uav

#endif
